use std::{borrow::Cow, fmt::Display};

use actix_web::{http::header::ContentType, HttpResponse, ResponseError};
use serde::Serialize;
use thiserror::Error;

use crate::web::cfg::{HTTP_RESP_FAIL, HTTP_RESP_HEAD};

pub type IoErr = std::io::Error;
pub type CborErr = serde_cbor::Error;
pub type SqlxErr = sqlx::Error;
pub type TimeoutErr = tokio::time::error::Elapsed;

#[derive(Debug, Error)]
pub enum AppErr {
    #[error("io:{0}")]
    Io(#[from] IoErr),

    #[error("cbor:{0}")]
    Cbor(#[from] CborErr),

    #[error("sqlx:{0}")]
    Sqlx(#[from] SqlxErr),

    #[error("timeout:{0}")]
    Timeout(#[from] TimeoutErr),

    #[error("{0}")]
    Custom(Cow<'static, str>),

    #[error("{0}")]
    Info(ErrInfo),
}

pub trait ErrWrap<T> {
    fn wrap(self) -> Result<T, AppErr>;

    fn wrap_msg(self, msg: &str) -> Result<T, AppErr>;
}

impl<T, E: std::error::Error> ErrWrap<T> for Result<T, E> {
    fn wrap(self) -> Result<T, AppErr> {
        match self {
            Ok(v) => Ok(v),
            Err(e) => Err(AppErr::Custom(Cow::Owned(e.to_string()))),
        }
    }

    fn wrap_msg(self, msg: &str) -> Result<T, AppErr> {
        match self {
            Ok(v) => Ok(v),
            Err(e) => {
                let msg = format!("{}:{}", msg, e.to_string());
                Err(AppErr::Custom(Cow::Owned(msg)))
            }
        }
    }
}

#[derive(Debug, Serialize)]
pub struct ErrInfo {
    err_code: i32,
    err_msg: Cow<'static, str>,
}

pub const EC_UNKNOWN: i32 = -1;
pub const EC_NO_ID: i32 = 1;
pub const EC_DEVICE_NOT_LOGIN: i32 = 2;
pub const EC_NO_EVENT: i32 = 3;
pub const EC_NO_USER: i32 = 4;
pub const EC_NO_DEVICE: i32 = 5;
pub const EC_PWD_FAIL: i32 = 6;
pub const EC_USER_NOT_LOGIN: i32 = 7;
pub const EC_USER_LOGIN_TIMEOUT: i32 = 8;
pub const EC_PRODUCT_IS_EXISTS: i32 = 9;
pub const EC_NO_IMAGE: i32 = 10;

impl Display for ErrInfo {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{} {}", self.err_code, self.err_msg)
    }
}

pub fn new_err_info(ec: i32, msg: &'static str) -> AppErr {
    AppErr::Info(ErrInfo {
        err_code: ec,
        err_msg: Cow::Borrowed(msg),
    })
}

pub fn new_err(msg: &'static str) -> AppErr {
    AppErr::Custom(Cow::Borrowed(msg))
}

impl ResponseError for AppErr {
    fn error_response(&self) -> HttpResponse {
        let body = match self {
            Self::Info(info) => serde_cbor::to_vec(info).unwrap(),

            _ => {
                let info = ErrInfo {
                    err_code: EC_UNKNOWN,
                    err_msg: Cow::Owned(self.to_string()),
                };
                serde_cbor::to_vec(&info).unwrap()
            }
        };

        HttpResponse::Ok()
            .content_type(ContentType::octet_stream())
            .insert_header((HTTP_RESP_HEAD, HTTP_RESP_FAIL))
            .body(body)
    }
}
